package zheezes.protocol.impl.gbw;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.log4j.Logger;

import zheezes.util.ByteUtil;


public abstract class DataUnit implements Comparable<DataUnit> {
	private static Logger logger = Logger.getLogger(DataUnit.class);
	
	protected DataUnitId id = new DataUnitId();
	private boolean hasData = true;
	private boolean override = false;
	
	public DataUnit() {
		// set pn
		id.setPn((short) 0);
		
		// set fn
		String name = getClass().getSimpleName().toLowerCase();
		StringBuilder sb = new StringBuilder();
		int idx = -1;
		if ((idx = name.indexOf("down")) > 0) {
			idx += 4;
		} else if ((idx = name.indexOf("up")) > 0) {
			idx += 2;
		}
		char c = '\0';
		if (idx++ > 0) {
			while (Character.isDigit((c = name.charAt(idx++)))) {
				sb.append(c);
			}
			id.setFn((short) Integer.parseInt(sb.toString()));
		}
	}

	public static final DataUnit getDataUnit(byte afn, short fn, int dir) {
		DataUnit du = null;

		StringBuilder sb = new StringBuilder();
		sb.append(String.format("%s.afn%02x.", DataUnit.class.getPackage().getName(), afn));
		sb.append(String.format("Afn%02X", afn));
		if (afn != 0x00) {
			sb.append(dir == 0 ? "Down" : "Up");
		}
		sb.append(String.format("F%d", fn));
		sb.append("DataUnit");

		try {
			Class clazz = Class.forName(sb.toString());
			int modifiers = clazz.getModifiers();
			if (!Modifier.isAbstract(modifiers)) {
				du = (DataUnit) clazz.newInstance();
			}
		} catch (Exception e) {
			logger.debug(e.getMessage(), e);
		}
		
		return du;
	}
	
	public static final DataUnit getDataUnit(byte afn, int dir, byte[] buf, int offset, int max) {
		DataUnit du = null;
		int idx = offset;
		DataUnitId id = new DataUnitId();
		idx += id.setBytes(buf, idx, max);
		StringBuilder sb = new StringBuilder();
		sb.append(String.format(DataUnit.class.getPackage().getName() + ".afn%02x.", afn));
		sb.append(String.format("Afn%02X", afn));
		if (afn != 0x00) {
			sb.append(dir == 0 ? "Down" : "Up");
		}
		sb.append(String.format("F%d", id.getFn()));
		sb.append("DataUnit");

		try {
			Class clazz = Class.forName(sb.toString());
			int modifiers = clazz.getModifiers();
			if (Modifier.isAbstract(modifiers)) {
				Method method = clazz.getMethod("select", new Class[] { byte[].class, Integer.TYPE });
				clazz = (Class) method.invoke(null, buf, idx);
			}
			du = (DataUnit) clazz.newInstance();
		} catch (Exception e) {
			logger.debug(e.getMessage(), e);
		}
		if (du != null) {
			du.setBytes(id, buf, idx, max);
		}

		return du;
	}

	public byte getAfn() {
		Class clazz = getClass();
		String name = clazz.getName();
		int idx = name.indexOf("Afn");
		return ByteUtil.cctoa(name.toCharArray(), idx + 3);
	}
	
	public boolean hasData() {
		return hasData;
	}

	public static final short[] getDataUnitId(byte[] buf, int offset) {
		int idx = offset;
		short pn = (short) 0xFFFF;
		if (buf[idx] == 0x00 && buf[idx + 1] == 0x00) {
			pn = 0;
		} else {
			pn = (short) (8 * ByteUtil.ffs(buf[idx + 1]) + ByteUtil.ffs(buf[idx]) + 1);
		}
		idx += 2;
		short fn = (short) (8 * buf[idx + 1] + ByteUtil.ffs(buf[idx]) + 1);
		idx += 2;

		return new short[] { pn, fn };
	}

	public abstract int sizeOfData();

	public int sizeof() {
		return id.sizeof() + sizeOfData();
	}

	public abstract int setData(byte[] buf, int offset, int max);

	public abstract int getData(byte[] buf, int offset);

	public int setBytes(DataUnitId id, byte[] buf, int offset, int max) {
		this.id = id;
		int idx = offset;
		if (idx < max) {
			idx += setData(buf, idx, max);
		} else {
			hasData = false;
		}

		return idx - offset;
	}

	public int setBytes(byte[] buf, int offset, int max) {
		int idx = offset;
		idx += id.setBytes(buf, idx, max);

		return setBytes(id, buf, idx, max);
	}

	public int getBytes(byte[] buf, int offset) {
		int idx = offset;
		idx += id.getBytes(buf, idx);
		if (hasData) {
			idx += getData(buf, idx);
		}

		return idx - offset;
	}

	public DataUnitId getId() {
		return id;
	}

	public void setId(DataUnitId id) {
		this.id = id;
	}

	public int compareTo(DataUnit other) {
		return id.compareTo(other.id);
	}

	public boolean isOverride() {
		return override;
	}

	public static void main(String[] args) {
		byte[] buf = new byte[] { (byte) 0x01, (byte) 0x80, 0x01, 30 };
		short[] id = getDataUnitId(buf, 0);
		System.out.printf("%d, %d", id[0], id[1]);
		
	}
}
